package nebula

import (
	"gddgame.cc/galaxy/satellite"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/cache"

	"errors"
	"fmt"
	"io"
	"net/url"
	"runtime/debug"
	"sync"

	"gddgame.cc/galaxy/utils/def"
)

var (
	ProtocalError = errors.New("protocal is error")
)

var (
	clientPool = sync.Pool{}
)

func init() {
	clientPool.New = func() interface{} {
		c := &clientInline{}
		c.init()
		return c
	}
}

// 消息通道，链接状态
type clientInline struct {
	def.Logger
	*cache.Store

	sid     string
	server  *Server
	gateway *satellite.Gateway
	pp      *satellite.PingPongServer

	app *AppPacket

	close  chan struct{}
	closed bool
	flow   bool

	nss sync.Map

	conn satellite.Conn
}

func newServerClient(e *Server, g *satellite.Gateway, sid string) *clientInline {
	c := clientPool.Get().(*clientInline)
	c.sid = sid
	c.server = e
	// 设定log标识
	c.Logger = e.logger.LoggerWrap(e.options.TrackId, sid)
	c.Store = e.cache.Store(sid)
	c.gateway = g
	c.closed = false
	c.flow = false

	c.initSession()
	return c
}

func (c *clientInline) init() {
	c.close = make(chan struct{}, 1)
	c.nss = sync.Map{}
	c.pp = satellite.NewPPServer(5, c)
}

func (c *clientInline) Release() {
	c.server.cache.Clean(c.sid)
	c.server = nil
	c.gateway = nil
	c.app = nil
	c.Store = nil

	c.init()
	clientPool.Put(c)
}

func (c *clientInline) initSession() {
	if c.server.store != nil {
		if err := c.server.store.Get(c.server.sessionKey+"/"+c.sid, &c.Store.Data); err != nil {
			c.Error(err)
		}
	}
}

func (c *clientInline) recover(err interface{}) {
	exception, ok := err.(error)
	if c.server.debug {
		debug.PrintStack()
	}
	if ok {
		c.onError(exception)
	} else {
		c.onError(errors.New(err.(string)))
	}
}

func (c *clientInline) SendPong(bytes []byte) {
	c.SendByte(P_PONG, false, bytes)
}

func (c *clientInline) Send(mt MT, compress bool, f func(writer io.Writer) error) {
	if c.closed {
		return
	}
	err := c.gateway.Write(byte(mt<<3&0xff), c.conn, compress, f)
	if err != nil {
		c.Error("Client send: ", err.Error())
	}
}

func (c *clientInline) SendData(mt MT, compress bool, data satellite.DataPacket) {
	c.Send(mt, compress, data.Encode)
}

func (c *clientInline) SendByte(mt MT, compress bool, data []byte) {
	c.Send(mt, compress, func(writer io.Writer) error {
		//fmt.Println(t, string(data))
		if data != nil {
			_, err := writer.Write(data)
			return err
		} else {
			return nil
		}
	})
}

func (c *clientInline) Packet(packet *NsData, ns *serverNamespace, compress bool) {
	if c != nil {
		//c.Debugf("[ Nebula ] client: %s send namespace %#v", c.sid, ns.nsp)
		// todo 同一个namespace进行队列处理，提高发送效率
		c.SendData(P_NAMESPACE, compress, &NsDataArray{packet})
	}
}

func (c *clientInline) Close() {
	if c.conn != nil {
		c.SendByte(P_CLOSE, false, nil)
	}
	//debug.PrintStack()
	c.destroy("forced server close")
}

func (c *clientInline) Disconnect(s *Socket) error {
	_, ok := c.nss.Load(s.Namespace)
	if !ok {
		return fmt.Errorf("no socket for namespace %s", s.Namespace.nsp)
	}
	c.nss.Delete(s.Namespace)
	return nil
}

func (c *clientInline) App() *AppPacket {
	return c.app
}
func (c *clientInline) With(nsp NSP) (*Socket, error) {
	ns, err := c.server.root.With(nsp)
	if ns == nil {
		return nil, err
	}
	i, ok := c.nss.Load(ns)
	if !ok {
		return nil, fmt.Errorf("no socket for namespace %s", ns.nsp)
	}
	return i.(*Socket), nil
}

func (c *clientInline) onConnect(s *Socket) {
	c.nss.Store(s.Namespace, s)
}

func (c *clientInline) valid(ns *serverNamespace, packet *NsData) {
	socket := newSocket(ns, c)
	if packet.Data != nil {
		u, err := url.Parse(packet.Data.(string))
		if err != nil {
			c.Packet(&NsData{Type: N_ERROR, NSP: packet.NSP, Data: err.Error()}, nil, false)
			return
		}

		m := make(map[string]string)
		if err := utils.ParseQuery(m, u.RawQuery); err != nil {
			c.Packet(&NsData{Type: N_ERROR, NSP: packet.NSP, Data: err.Error()}, nil, false)
		} else {
			socket.query = m
		}
	}

	c.Debugf("[ Nebula ] client: %s connect namespace %#v", c.sid, ns.nsp)
	// ns.nsp = packet.NSP
	ns.socketChannel <- socket
	c.nss.Store(socket.Namespace, socket)
}

func (c *clientInline) ID() string {
	return c.sid
}

func (c *clientInline) Connected() bool {
	return !c.closed
}

func (c *clientInline) onOpen(conn satellite.Conn) {
	c.conn = conn
	c.SendByte(P_OPEN, false, []byte(c.sid))
}

func (c *clientInline) onReopen(conn satellite.Conn) {
	c.conn = conn
	c.SendByte(P_REOPEN, false, []byte(c.sid))
}

func (c *clientInline) OnClose(conn satellite.Conn) {
	c.onClose()
}

func (c *clientInline) onClose() {
	c.destroy("client close")
}

func (c *clientInline) onNamespace(message satellite.Message) {
	//defer func() {
	//	if err := recover(); err != nil {
	//		c.recover(err)
	//	}
	//}()
	p := GetNsData()
	message.ReadData(p)
	//c.engine.Debugf("client: %#v accept namespace %#v", c.sid, p)
	for _, d := range *p {
		c.onNamespaceDecode(d)
	}
	p.Release()
}

func (c *clientInline) onApplication(message satellite.Message) {
	//defer func() {
	//	if err := recover(); err != nil {
	//		c.recover(err)
	//	}
	//}()
	if c.app != nil {
		c.Debugf("[ Nebula ] application is exist:%s", c.sid)
	}
	p := GetApp()
	message.ReadData(p)
	c.Debugf("[ Nebula ] client: %s accept application %#v", c.sid, p)
	c.app = p
}

func (c *clientInline) OnAccept(conn satellite.Conn, code byte, message satellite.Message) error {
	//c.Debugf("[ Nebula ] client: %s accept", c.sid)
	t := MT(code >> 3)

	if c.closed {
		return nil
	}
	if !c.flow {
		switch t {
		case P_OPEN:
			c.Debugf("[ Nebula ] client: %s accept open", c.sid)
			c.onOpen(conn)
		case P_REOPEN:
			c.Debugf("[ Nebula ] client: %s accept reopen", c.sid)
			c.onReopen(conn)
		case P_CLOSE:
			c.Debugf("[ Nebula ] client: %s accept close", c.sid)
			c.onClose()
		default:
			c.Debugf("[ Nebula ] client: %s accept %s", c.sid, t)
			// 流程错误，直接关闭链接
			c.Close()
			return ProtocalError
		}
		c.flow = true
	} else {
		switch t {
		case P_CLOSE:
			c.Debugf("[ Nebula ] client: %s accept close", c.sid)
			c.onClose()
		case P_PING:
			//c.Debugf("[ Nebula ] client: %s accept ping", c.sid)
			c.pp.OnPing()
		case P_PONG:
			//c.Debugf("[ Nebula ] client: %s accept pong", c.sid)
			c.pp.OnPong(message)
		case P_APPLICATION:
			//c.server.Debugf("client: %s accept application", c.sid)
			c.onApplication(message)
		case P_NAMESPACE:
			//c.Debugf("client: %s accept namespace", c.sid)
			c.onNamespace(message)
		default:
			return ProtocalError
		}
	}
	return nil
}

func (c *clientInline) onNamespaceDecode(packet *NsData) {
	//c.Debugf("client: %#v accept %#v namespace", c.sid, packet.Type, packet.NSP)
	ns, _ := c.server.root.With(packet.NSP)
	if ns == nil {
		c.Packet(&NsData{Type: N_ERROR, NSP: packet.NSP, Data: fmt.Sprintf("invalid namespace %s", packet.NSP)}, nil, false)
		return
	}
	if packet.Type == N_CONNECT {
		c.valid(ns, packet)
	} else {
		i, ok := c.nss.Load(ns)
		if ok {
			s := i.(*Socket)
			switch packet.Type {
			case N_EVENT:
				s.onEvent(packet)
			case N_ACK:
				s.onAck(packet)
			case N_DISCONNECT:
				s.onDisconnect()
			case N_ERROR:
				s.onError(packet)
			}
		} else {
			c.Debugf("[ Nebula ] no socket for namespace %#v", packet)
		}
	}
}

func (c *clientInline) onError(err error) {
	debug.PrintStack()
	c.Errorf("Client error:%s", err.Error())
	//c.destroy("client data error")
}

func (c *clientInline) destroy(reason string) {
	if c.closed {
		return
	}
	c.closed = true
	close(c.close)
	c.Debugf("[ Nebula ] Client :%s destroy %s", c.sid, reason)
	c.nss.Range(func(key, value interface{}) bool {
		value.(*Socket).onDisconnect()
		return true
	})
	if c.conn != nil {
		_ = c.conn.Close()
	}
	if c.server.store != nil {
		_ = c.server.store.Set(c.server.sessionKey+"/"+c.sid, c.Store.Data)
	}
	c.gateway.Disconnect(c.sid)
	c.app.Release()
	c.Release()
}
